package fregat.connector;

import fregat.base.database.DatabaseConnector;
import fregat.base.net.Protocol;
import fregat.base.util.ConfigurationBundle;
import fregat.base.util.Logger;
import fregat.connector.base.ASCIIProtocol;
import fregat.connector.channel.AbstractChannel;
import fregat.connector.channel.ChannelListener;

import java.util.*;

/**
 * Configuration for the connector service.
 *
 * @author Alexander Mann <br><b><i>ECO_L O G</i> &nbsp Logistiksysteme GmbH</b>
 * @version 1.0
 */
public class ConnectorConfiguration extends ConfigurationBundle {
  private Map channels; // map of configured channels
  private Map workers; // map of configured workers

  /**
   * Creates a new configuration using the configuration keys from the specified
   * resource bundle.
   *
   * @param configurationBundle Path to configuration resource bundle
   */
  public ConnectorConfiguration(String configurationBundle) {
    super(configurationBundle);

    if (getConfigurationString("logger.config") == null) {
      configureLogger(Logger.DEFAULT_CATEGORY);
      configureLogger(ConnectorService.LOGGING_CATEGORY);
    }
  }

  /**
   * @param name Configuration name of channel
   * @return True, if is configured as server
   */
  protected boolean isServer(String name) {
    String type = getType(name);
    return (type != null && type.equalsIgnoreCase("server"));
  }

  private String getType(String name) {
    return getConfigurationString("channel.config." + name + ".type");
  }

  /**
   * Creates and configures the used TCP/IP protocol.
   *
   * @param name
   * @return
   */
  public Protocol configureProtocol(String name) {
    Protocol protocol;

    String configPath = "channel.config." + name + ".protocol";
    String className = getStringValue(configPath, "connector.protocol", "class");
    String debug = ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_7");

    if (className == null) {
      protocol = new ASCIIProtocol(name);
      int fixedLength = getIntegerValue(configPath, "connector.protocol", "fixed.length");
      int headerLength = getIntegerValue(configPath, "connector.protocol", "header.length");
      String startDelimiter = getStringValue(configPath, "connector.protocol", "delimiter.start");
      String endDelimiter = getStringValue(configPath, "connector.protocol", "delimiter.end");

      if (startDelimiter != null || endDelimiter != null) {
        // character sequence as start and end delimiter
        boolean startIncluded = getBooleanValue(configPath, "connector.protocol", "delimiter.start.included");
        boolean endIncluded = getBooleanValue(configPath, "connector.protocol", "delimiter.end.included");

        ((ASCIIProtocol) protocol).setDelimiters(startDelimiter, startIncluded,
          endDelimiter, endIncluded);
        String log = "";
        if (startDelimiter != null) {
          log = ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_8").replaceFirst("%1",
            ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_10"));
          log = log.replaceFirst("%2", startDelimiter);
          if (startIncluded) {
            log += " " + ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_9");
          }
        }

        if (endDelimiter != null) {
          if (startDelimiter != null) {
            log += "\n";
          } else {
            log = "";
          }
          log += ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_8").replaceFirst("%1",
            ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_11"));
          log = log.replaceFirst("%2", endDelimiter);
          if (endIncluded) {
            log += " " + ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_9");
          }
        }
        System.out.println(log);
      }

      if (fixedLength > 0) {
        // fixed length for all telegrams
        String filler = getStringValue(configPath, "connector.protocol", "fixed.filler");
        if (filler == null) {
          filler = " ";
        }
        ((ASCIIProtocol) protocol).setFixedLength(fixedLength, filler);
        debug = ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_2").replaceFirst("%1", Integer.toString(fixedLength));
        debug = debug.replaceFirst("%2", filler);

      } else if (headerLength > 0) {
        // length of telegram is specified in a header
        int lengthPosition = getIntegerValue(configPath, "connector.protocol", "header.position");
        int lengthDigits = getIntegerValue(configPath, "connector.protocol", "header.digits");
        boolean exclusiveHeader = getBooleanValue(configPath, "connector.protocol", "header.exclusive");
        if (lengthDigits > 0) {
          ((ASCIIProtocol) protocol).setHeader(headerLength, lengthPosition, lengthDigits, exclusiveHeader);
          debug = ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_3").replaceFirst("%1", Integer.toString(lengthDigits));
          debug = debug.replaceFirst("%2", Integer.toString(lengthPosition));
          debug = debug.replaceFirst("%4", Integer.toString(headerLength));
          debug = debug.replaceFirst("%3",
            (exclusiveHeader ?
            ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_6") :
            ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_5")));

        } else {
          System.out.println(ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_1"));
        }
      }
    } else {
      if (getConfigurationString(configPath + ".class") == null) {
        configPath = "connector.protocol";
      }
      protocol = (Protocol) createObject(configPath, name, "java.lang.String");
      debug = ConnectorService.resource.getString("ConnectorConfiguration_configureProtocol_4").replaceFirst("%1", className);
    }
    System.out.println(debug);
    return protocol;
  }

  /**
   * Creates the channels and workers specified in configuration file.
   */
  protected void configureChannels() {
    String name;

    // worker configuration
    workers = new Hashtable();
    Enumeration e = getConfigurationEnumeration("worker.config", ",", STRING);
    while (e.hasMoreElements()) {
      // create worker
      name = (String) e.nextElement();
      workers.put(name, createObject("worker.config." + name, name));
    }

    // channel configuration
    channels = new LinkedHashMap();
    e = getConfigurationEnumeration("channel.config", ",", STRING);
    while (e.hasMoreElements()) {
      // create channel
      name = (String) e.nextElement();
      createChannel(name);
    }
  }

  /**
   * Creates the channel with the specified name.
   *
   * @param name Configuration name of channel
   * @return Server or client channel
   */
  public AbstractChannel createChannel(String name) {
    AbstractChannel channel;
    String debug;
    String configPath = "channel.config." + name;
    System.out.println(ConnectorService.resource.getString("ConnectorConfiguration_createChannel_1") + " '" + name + "' ...");

    channel = (AbstractChannel) createObject(configPath, name, "java.lang.String");

    Enumeration e = getConfigurationEnumeration("channel.config." + name + ".worker", ",", STRING);
    while (e.hasMoreElements()) {
      ChannelListener worker = (ChannelListener) workers.get((String) e.nextElement());
      if (worker != null) {
        channel.addListener(worker);
      }
    }
    channels.put(name, channel);

    return channel;
  }

  /**
   * @return Collection of channels
   */
  public Iterator channels() {
    return channels.values().iterator();
  }

  /**
   * @return Collection of workers
   */
  public Iterator workers() {
    return workers.values().iterator();
  }

  public AbstractChannel getChannel(String name) {
    return (AbstractChannel) channels.get(name);
  }

  public Object getWorker(String name) {
    return workers.get(name);
  }

  public boolean containsChannel(String name) {
    return channels.containsKey(name);
  }

  public boolean containsWorker(String name) {
    return workers.containsKey(name);
  }

  /**
   * Configures and returns a database connector using the configuration keys
   * <code>
   * <li>configPath.db.name</li>
   * <li>configPath.db.user</li>
   * <li>configPath.db.password</li>
   * </code>
   * If key is not found the default config path <code>connector.db</code> is
   * used.<br>
   * If even the default keys are missing, the default user and database
   * <code>sysadm@lms</code> is used.
   *
   * @param configPath Path to configuration keys
   * @return Database connection wrapper
   */
  public DatabaseConnector configureDatabase(String configPath) {

    String value = getConfigurationString(configPath + ".db.name");
    if (value == null) {
      return new DatabaseConnector(this,"connector",Logger.getCategory(ConnectorService.LOGGING_CATEGORY));
    } else {
      return new DatabaseConnector(this,configPath,Logger.getCategory(ConnectorService.LOGGING_CATEGORY));
    }
  }

  /**
   * Returns the special or default configured value of the given key.
   *
   * @param configPath  Path to configuration keys
   * @param defaultPath Path to default configuration keys
   * @param key         Configuration key
   * @return Configured value
   */
  private String getStringValue(String configPath, String defaultPath, String key) {
    String value = getConfigurationString(configPath + "." + key);
    if (value == null) {
      value = getConfigurationString(defaultPath + "." + key);
    }
    return value;
  }

  /**
   * Returns the special or default configured value of the given key.
   *
   * @param configPath  Path to configuration keys
   * @param defaultPath Path to default configuration keys
   * @param key         Configuration key
   * @return Configured value
   */
  private int getIntegerValue(String configPath, String defaultPath, String key) {
    int value = getConfigurationInteger(configPath + "." + key);
    if (value < 0) {
      value = getConfigurationInteger(defaultPath + "." + key);
    }
    return value;
  }

  /**
   * Returns the special or default configured value of the given key.
   *
   * @param configPath  Path to configuration keys
   * @param defaultPath Path to default configuration keys
   * @param key         Configuration key
   * @return Configured value
   */
  private boolean getBooleanValue(String configPath, String defaultPath, String key) {
    boolean value = getConfigurationBoolean(configPath + "." + key);
    if (!value) {
      value = getConfigurationBoolean(defaultPath + "." + key);
    }
    return value;
  }

  /**
   * Returns the special or default configured value of the given key.
   *
   * @param configPath  Path to configuration keys
   * @param defaultPath Path to default configuration keys
   * @param key         Configuration key
   * @return Configured value
   */
  private long getLongValue(String configPath, String defaultPath, String key) {
    long value = getConfigurationLong(configPath + "." + key);
    if (value < 0) {
      value = getConfigurationLong(defaultPath + "." + key);
    }
    return value;
  }
}
